package info.xiaomo.gengine.logger;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import info.xiaomo.gengine.logger.annotation.Column;
import info.xiaomo.gengine.logger.annotation.LogTable;
import info.xiaomo.gengine.logger.desc.ColumnDesc;
import info.xiaomo.gengine.logger.desc.TableDesc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 日志基类
 *
 * @author 张力
 */
@LogTable(cycle = TableCycle.SINGLE)
public abstract class AbstractLog implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLog.class);

    private static final Map<Class<?>, TableDesc> TABLE_DESC_MAP = new HashMap<>(10);

    void init() throws Exception {

        Class<?> clazz = this.getClass();
        LogTable logTable = clazz.getAnnotation(LogTable.class);
        if (logTable == null) {
            return;
        }
        TableDesc desc = new TableDesc();
        desc.setCycle(logTable.cycle());
        String tableName = logTable.tableName();
        if ("".equals(tableName)) {
            tableName = clazz.getSimpleName();
        }
        desc.setName(tableName);
        desc.setPrimaryKey(logTable.primaryKey());

        // 找出所有父类，父类的属性先遍历，所以将顶级父类放在了列表第一个
        List<Class<?>> clazzList = new ArrayList<>();
        while (clazz.getSuperclass() != null && clazz != Object.class) {
            clazzList.add(0, clazz);
            clazz = clazz.getSuperclass();
        }

        // 遍历所有类（包括父类），获取日志属性
        for (Class<?> cl : clazzList) {
            Field[] fields = cl.getDeclaredFields();
            for (Field field : fields) {
                Column column = field.getAnnotation(Column.class);
                if (column == null) {
                    continue;
                    // 全部使用默认值
                }

                //				PropertyDescriptor pd = new PropertyDescriptor(field.getName(), cl);
                //				Method readMethod = pd.getReadMethod();
                Method readMethod = field.getClass().getMethod(field.getName(), cl);
                ColumnDesc colDesc = new ColumnDesc();
                colDesc.setAllowNull(column.allowNull());
                colDesc.setAutoIncrement(column.autoIncrement());
                String colName = column.colName();
                if ("".equals(colName)) {
                    colName = field.getName();
                }
                colDesc.setName(colName.toLowerCase());
                colDesc.setReadMethod(readMethod);
                colDesc.setSize(column.size());
                colDesc.setType(column.fieldType().name().toLowerCase());
                colDesc.setCommit(column.commit());
                desc.addCol(colDesc);
            }
        }
        desc.init();
        TABLE_DESC_MAP.put(this.getClass(), desc);
        checkTable();
    }

    private String buildCreateSql() {
        return String.format(
                TABLE_DESC_MAP.get(this.getClass()).getCreateSql(),
                TABLE_DESC_MAP.get(this.getClass()).buildName(System.currentTimeMillis()));
    }

    String buildInsertSQL() {
        return String.format(
                TABLE_DESC_MAP.get(this.getClass()).getInsertSql(),
                TABLE_DESC_MAP.get(this.getClass()).buildName(System.currentTimeMillis()));
    }

    Object[] buildInsertParam() {
        return TABLE_DESC_MAP.get(this.getClass()).buildInsertParam(this);
    }

    private void checkTable() throws Exception {
        String buildName =
                TABLE_DESC_MAP.get(this.getClass()).buildName(System.currentTimeMillis());
        LOGGER.info("检测查表" + buildName);
        Connection connection = null;
        Statement statement = null;
        try {
            connection = LogService.template.getPool().getConnection();
            List<String> tableNames = getTableName(connection);
            if (tableNames.contains(buildName)) {
                // 原有表结构
                List<ColumnDesc> columnDefine = getColumnDefine(connection, buildName);
                // 表的主键
                List<String> primaryKeys = getTablePrimaryKeys(connection, buildName);
                // 去除主键
                // 主键不进行变更
                columnDefine.removeIf(columnDesc -> primaryKeys.contains(columnDesc.getName()));

                // 去除主键
                List<ColumnDesc> newColumns = new ArrayList<>();
                for (ColumnDesc col : TABLE_DESC_MAP.get(this.getClass()).getColumns()) {
                    if (!TABLE_DESC_MAP
                            .get(this.getClass())
                            .getPrimaryKey()
                            .equals(col.getName())) {
                        newColumns.add(col);
                    }
                }

                List<String> compartor =
                        TableCompareUtil.compare(buildName, newColumns, columnDefine);
                if (compartor.size() > 0) {
                    statement = connection.createStatement();
                    for (String string : compartor) {
                        LOGGER.info("检查到变更：" + string);
                        statement.addBatch(string);
                    }
                    statement.executeBatch();
                }
            } else {
                // 当前数据库没有这个表,不按时间周期滚动的表，预先创建
                if (TABLE_DESC_MAP.get(this.getClass()).getCycle() == TableCycle.SINGLE) {
                    String createSql = this.buildCreateSql();
                    try {
                        LogService.template.update(createSql);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            LOGGER.info(buildName + "检查结束");
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    LOGGER.error("关闭statement失败", e);
                }
            }
            if (connection != null) {
                LogService.template.getPool().release(connection);
            }
        }
    }

    private List<ColumnDesc> getColumnDefine(Connection conn, String tableName)
            throws SQLException {
        DatabaseMetaData metaData = conn.getMetaData();
        ResultSet columns = metaData.getColumns(null, "%", tableName, "%");
        List<ColumnDesc> infos = new ArrayList<>();
        while (columns.next()) {
            ColumnDesc info = new ColumnDesc();
            info.setName(columns.getString("COLUMN_NAME").toLowerCase());
            info.setType(columns.getString("TYPE_NAME").toLowerCase());
            info.setSize(columns.getInt("COLUMN_SIZE"));
            info.setAllowNull(columns.getBoolean("IS_NULLABLE"));
            infos.add(info);
        }

        return infos;
    }

    private List<String> getTablePrimaryKeys(Connection conn, String tableName)
            throws SQLException {
        DatabaseMetaData metaData = conn.getMetaData();
        ResultSet rs = metaData.getPrimaryKeys(null, "%", tableName);
        List<String> ret = new ArrayList<>();
        while (rs.next()) {
            ret.add(rs.getString("COLUMN_NAME"));
        }

        return ret;
    }

    private List<String> getTableName(Connection conn) throws SQLException {
        ResultSet tableRet = conn.getMetaData().getTables(null, null, null, null);
        List<String> tableNames = new ArrayList<>();
        while (tableRet.next()) {
            tableNames.add(tableRet.getString("TABLE_NAME"));
        }
        return tableNames;
    }

    @Override
    public void run() {
        String insertSql = this.buildInsertSQL();
        Object[] params = this.buildInsertParam();
        try {
            // 插入
            int ret = LogService.template.update(insertSql, params);
            if (ret < 0) {
                // 建表
                String createSql = this.buildCreateSql();
                LogService.template.update(createSql);
                LogService.template.update(insertSql, params);
            }
        } catch (Exception e) {
            LOGGER.error("日志入库执行失败sql:" + insertSql + ",params:" + Arrays.toString(params), e);
        }
    }
}
